package ar.uba.fi.tonyvaliente.files;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;

public class FileBlock {
	
	private int capacity;
	private ArrayList<FileRecord> records;
	private int freeBytes;

	private static final int DEFAULT_CAPACITY = 4096;
	
	public FileBlock() {
		this(DEFAULT_CAPACITY);
	}
	
	public FileBlock(int capacity) {
		this.capacity = capacity;
		this.freeBytes = capacity - 8; // 8 bytes de recordCount y freeBytes
		this.records = new ArrayList<FileRecord>();
	}
	
	public static FileBlock createFromRawBytes(byte[] rawBytes) throws FileException {
		FileBlock block = new FileBlock(rawBytes.length);
		
		ByteArrayInputStream bais = new ByteArrayInputStream(rawBytes);
		DataInputStream in = new DataInputStream(bais);
		
		try {
			// Leo la cantidad de registros en el bloque
			int recordCount = in.readInt();
			
			// Salteo el espacio libre
			in.readInt();
			
			// Leo la cantidad de registros indicada por recordCount
			int bytesRead;
			for (int i = 0; i < recordCount; i++) {
				int dataSize = in.readInt();
				byte[] data = new byte[dataSize];
				bytesRead = in.read(data);
				if (bytesRead == dataSize) {
					block.appendRecord(new FileRecord(data));
				} else {
					throw new FileException("Error al deserializar FileBlock: "
							+ "los bytes leidos de un FileRecord no coinciden "
							+ "con la longitud leida para el mismo.");
				}
			}
			
			in.close();
			
		} catch (IOException e) {
			throw new FileException("Error al deserializar un FileBlock", e);
		}
		
		return block;
	}
	
	public int getCapacity() {
		return capacity;
	}
	
	public int getRecordCount() {
		return records.size();
	}
	
	public int getFreeBytes() {
		return freeBytes;
	}
	
	public FileRecord getRecord(int pos) {
		return (FileRecord)records.get(pos).clone();
	}
	
	public boolean appendRecord(FileRecord r) {
		if (freeBytes >= r.getData().length + 4) {
			records.add(r);
			freeBytes -= r.getData().length + 4;
			return true;
		} else {
			return false;
		}
	}
	
	public boolean updateRecord(int pos, FileRecord r) {
		int oldRecordSize = records.get(pos).getData().length;
		int newRecordSize = r.getData().length;
		if (freeBytes + oldRecordSize >= newRecordSize) {
			freeBytes += oldRecordSize - newRecordSize;
			records.set(pos, r);
			return true;
		} else {
			return false;
		}
	}
	
	public void eraseRecord(int pos) {
		int bytesToFree = records.get(pos).getData().length + 4;
		records.remove(pos);
		freeBytes += bytesToFree;
	}
	
	public byte[] toRawBytes() {
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			DataOutputStream out = new DataOutputStream(baos);
			
			int freeRawBytes = this.capacity;
			
			// Escribo cantidad de registros
			out.writeInt(records.size());
			freeRawBytes -= 4;
			
			// Escribo espacio libre
			out.writeInt(freeBytes);
			freeRawBytes -= 4;
			
			// Escribo registros
			for (int i = 0; i < records.size(); i++) {
				byte[] data = records.get(i).getData();
				out.writeInt(data.length);
				out.write(data);
				freeRawBytes -= 4 + data.length;
			}
			
			// Relleno con ceros el espacio libre
			for (int i = 0; i < freeRawBytes; i++) {
				out.writeByte(0);
			}
			
			out.close();
			
			return baos.toByteArray();
			
		} catch (IOException e) {
			throw new RuntimeException("Error al serializar un FileBlock", e);
		}
	}
}
